Skip to content

Geonet: Update to Version 1.#1388

Closed
danieluliedguevara wants to merge 1 commit into
the-tcpdump-group:masterfrom
danieluliedguevara:master
Closed

Geonet: Update to Version 1.#1388
danieluliedguevara wants to merge 1 commit into
the-tcpdump-group:masterfrom
danieluliedguevara:master

Conversation

@danieluliedguevara
Copy link
Copy Markdown

Previous GeoNet was outdated and didn't comply with current standards, making tcpdump wrongly dissect GeoNet packets (issue) .

This PR updates ETSI GeoNetworking protocol to version 1 (ETSI EN 302 636-4-1 V1.4.1 (2020-01)). The update includes dissectors for GeoNet Beacon messages and TopoScopeBcast-SH. Additionally, the Basic Transport Protocol (ETSI EN 302 636-5-1 V2.2.1 (2019-05)) has also been updated. It includes BTP-A and BTP-B in all its variants.

@infrastation
Copy link
Copy Markdown
Member

Please see CONTRIBUTING.md, specifically:

touch .devel
./build_matrix.sh

After all the fix-ups have been made, please make this one clean commit.

@infrastation
Copy link
Copy Markdown
Member

Thank you. At a glance, this code needs to use tok2str() more instead of implementing a number of functions that take an integer and return a string, and needs to use named constants instead of hard-coding integer values. This problem isn't new, but in the old code it is much smaller because the old code is much smaller.

Also the use of clock_gettime() does not look right. For any protocol decoder the result is supposed to be a function of the packet data only (except reverse lookups in DNS when enabled).

Also most of the details that are now in the pull request message concern the commit, not the pull request, so should be in the commit message.

Also this removes support for GeoNet version 0 altogether, correct? And the new code prints the version, but does not check that it is version 1, correct?

@infrastation
Copy link
Copy Markdown
Member

Also this removes 4 existing tests.

@danieluliedguevara
Copy link
Copy Markdown
Author

I'll look into using tok2str() for some of the functions and also using make sure to use more constants. I'll also modify the commit message once I solve the issues.

As for the use of clock_gettime(), the GeoNet message header includes a "timestamp", the timestamp, in milliseconds, is encoded in 32 bits and hence can only express aproximately 49 days. So, instead of printing the raw value of the timestamp, which is of little use, I process it and generate the actual (human-readable) timestamp. Is this okay?

This does remove support for GeoNet version 0. There are two things I would point out, one is that GeoNet version 0 was drafted for a preliminary ETSI technical spec (as mentioned in this issue) and a new version was published in 2013 (12 years ago). Second, seeing that GeoNet-related services are rare, and all providers follow current standards I think it's better to remove support for GeoNet version 0 (for simplicity).

You are correct, the current PR doesn't check it's version 1... I'll make sure to fix that.

In the meantime, can you confirm that it's okay to remove support for GeoNet version 0? I can add it, but don't know if it will be used much.

@infrastation
Copy link
Copy Markdown
Member

Thank you for explaining. Regarding the 32-bit TST field, the standard defines it to wrap this often and to have this much precision, it does not include a notion of a calendar date or a wrap counter. It would be wrong for a protocol decoder to present the TS field as if it has more data than it actually has. If the user needs to interpret the recorded 32-bit value of TST relative to "real" date and time, that's what the libpcap packet timestamp is for. So the best this decoder could do is to print TST exactly as it is (an integer number of milliseconds, or something formatted as "seconds.millieconds" or "dd:hh:mm:ss.ms" or whatever is the common notation for this 32-bit value in this practice).

Regarding the versions, to comprehend this better, I had a look at the following specifications available at etsi.org:

As far as these documents define it, version 1 did not exist until 2017. As far as I understand V1.2.1 Section 8.4, digital signatures are optional. So, seemingly, version 0 could be not entirely irrelevant, but the problem is, the two version 0 specifications indeed define GeoNet packet structure differently:

  • TS 102 636-4-1 V1.1.1 Section 8.3 says it is Common Header plus Extended header. Ibid., Section 8.5.1 says Version is a Common Header field.
  • EN 302 636-4-1 V1.2.1 Section 8.3 says it is Basic Header plus Common Header plus Extended Header (optional). Ibid., Section 8.6.1 says Version is a Basic Header field.
  • Beyond the first octet (Version + NH) the V1.1.1 Common Header and V1.2.1 Basic Header diverge in their structure.

This way, I agree that the best move is to lose the current V1.1.1 (version 0) code in the tcpdump decoder, whether anybody gets to implement V1.2.1 (version 0) code in future or not.

@danieluliedguevara
Copy link
Copy Markdown
Author

Perfect!

I've done the following changes;

  1. Deleted clock_gettime() and now print TST in milliseconds.
  2. Used tok2str() instead of custom functions in all possible cases.
  3. Checked that the version field is indeed 1.
  4. Added more constants (replacing hardcoded integers).

The GeoNet header has a lot of fields encoded in smaller than 1 byte (i.e., 1-7 bits) values. Achieving pefectly clean code is difficult, since there is a lot of big masking and shifting.

Comment thread print-geonet.c Outdated

const char *next_header_text = basic_header_next_header_text_from_bytes(*next_header);
version = (value >> (4 + THREE_BYTES)) & FOUR_BITS_MASK;
if (!memchr(implemented_gn_versions, version, IMPLEMENTED_GN_VERSIONS_NUM))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that memchr(), at least if used this way, does not work as expected on big-endian architectures. A simple if or switch would be sufficient. The same likely applies to NH below.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok! I'll make sure to fix that.

Comment thread print-geonet.c Outdated
reserved = (value >> (TWO_BYTES)) & EIGHT_BITS_MASK;
lt_multiplier = (value >> (2 + ONE_BYTE)) & SIX_BITS_MASK;
lt_base = (value >> ONE_BYTE) & THREE_BITS_MASK;
rhl = value & FOUR_BITS_MASK;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fetching the entire header as a 32-bit integer does not work well. One common way to do that would be using a structure, and another would be just fetching one octet at a time, for example:

// Validate that length is at least 4.
uint8_t ver_nh = GET_U_1(*bp);
version = ver_nh & 0xf0;
nh = ver_nh & 0x0f;
*bp++;
*length--;

reserved = GET_U_1(*bp);
*bp++;
*length--;

lt = GET_U_1(*bp);
lt_multiplier = lt & 0x3f;
lt_base = (lt & 0xc0) >> 6;
*bp++;
*length--;

rhl = GET_U_1(*bp);
*bp++;
*length--;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference, that's ETSI EN 302 636-4-1 V1.4.1 Section 9.6. It would be useful to say that in a comment before the function to make it clear what it implements.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok! A structure would be great but since there are many <8 bit values I thing going with fetching one octet at a time is better.

Out of curiosity, what do you mean with "Fetching the entire header as a 32-bit integer does not work well."?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference should be easy to see if you compare the complexity of code. Also rhl = value & FOUR_BITS_MASK is wrong because RHL is an 8-bit integer.

Comment thread print-geonet.c

const char *next_header_text = tok2str(common_header_next_header_values, "Unknown", *next_header);
const char *header_type_text = tok2str(header_type_tok, "Unknown", HT_HST(*header_type, *header_subtype));
const char *flags_text = tok2str(flags_text_from_bytes, "Unknown", flags);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, the Common Header should have been a structure or a series of 1-octet and 2-octet fetches, not a 64-bit fetch. Section 9.7 Ibid.

@infrastation
Copy link
Copy Markdown
Member

Please make it one clean commit and wrap the commit message at 72 columns, "as was the style at the time".

Comment thread print-geonet.c Outdated
if (ndo->ndo_vflag > NDO_V_FLAG_FIRST_DEBUG_LEVEL)
{
ND_PRINT("GN_ADDR:%s tst:%s lat:%u lon:%u pai:%u, s:%u, h:%u; ", process_gn_addr(ndo, gn_addr), process_tst(tst), lat, lon, pai, s, h);
ND_PRINT("GN_ADDR:%s tst:%u lat:%u lon:%u pai:%u, s:%u, h:%u; ", process_gn_addr(ndo, gn_addr), tst, lat, lon, pai, s, h);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lat, Long and S are signed, please fix. For Lat and Long it may be useful to convert the sign to N/S/E/W. For all three (and H as well) it may be useful to print using a decimal dot to make it easier to read degrees and metres per second. See arista_print_date_hms_time() for an example.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed too. Also added for PAI (true or false string).

Comment thread print-geonet.c Outdated
static const struct tok common_header_next_header_values[] = {
{0, "Any"},
{1, "BTP-A"},
{2, "BTP-B"},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these 1 and 2 the BTP_A and BTP_B below?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, they are! I've rearranged the definitions to use the BTP_Aand BTP_B definitions.

Comment thread print-geonet.c
{0, "Any"},
{1, "BTP-A"},
{2, "BTP-B"},
{3, "IPv6"},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please either terminate every array of struct tok with a { 0, NULL } record or switch to struct uint_tokary and uint2tokary(), otherwise this code will crash.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added { 0, NULL } to all struct tok.

Comment thread print-geonet.c Outdated
case 3: // 100 seconds
base_seconds = 100.0;
break;
default: // default to 0 second
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This default case is dead code (LT Base is a 2-bit value). Perhaps this should have been a comment.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the comments for the LT Base values 0, 1, 2 and 3 indicate it would make sense to use named constants.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added constants and deleted the dead code.

Comment thread print-geonet.c Outdated
base_seconds = 0.0;
break;
}
return (u_int)(base_seconds * lt_multiplier);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When LT Base is 50ms, handling LT as an integer (rather than a float) discards any sub-second precision. It would be better to print LT Base and LT Multiplier separately to make it clear what is in the packet (if the product is wrong, it is desirable to know whether the base is off or the multiplier is off or both), after that it may be convenient to print LT (the product) as a decimal with two significant figures after the dot.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the float issue. I'm now returning a float and printing out a decimal with two significant figures.

Currently, the LT product is printed out when no verbose level is active. However, for verbose levels greater than 1 both the LT base and LT multiplier are printed out.

Comment thread print-geonet.c Outdated
u_int lt_seconds = convert_lt_to_seconds(lt_base, lt_multiplier);
if (ndo->ndo_vflag == NDO_V_FLAG_FIRST_DEBUG_LEVEL)
{
ND_PRINT("ver:%u nh:%s lt:%us rhl:%u; ",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I meant (LT ought to be a float or a string).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment thread print-geonet.c Outdated
}
else if (ndo->ndo_vflag > NDO_V_FLAG_FIRST_DEBUG_LEVEL)
{
ND_PRINT("ver:%u nh:%s reserved:%u lt:[base:%u mult:%u = %us] rhl:%u; ",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this. Also here LT base could be decoded using tok2str(). Also it may be simpler to print LT using the same format in all cases, unless there is a good reason to make the verbosity granular.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the advantages of decoding LT base with tok2str()?

LT Base is a two bit value used to find the corresponding time value (0.05, 1, 10, 100 seconds) to be used to find the Lifetime of the packet. So having it in str format is only useful for printing purposes.

I agree its simpler to print out the same information. But on the other hand, I think having LT in it's simplest form for low verbosity levels and having the full values of LT for higher verbose levels is better.

Copy link
Copy Markdown
Member

@infrastation infrastation left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this is good progress, thank you. This code requires a bit more work before it is ready.

This PR updates ETSI GeoNetworking protocol to version 1 (ETSI EN 302 636-4-1 V1.4.1 (2020-01)). The update includes dissectors for GeoNet Beacon messages and TopoScopeBcast-SH. Additionally, the Basic Transport Protocol (ETSI EN 302 636-5-1 V2.2.1 (2019-05)) has also been updated. It includes BTP-A and BTP-B in all its variants.
@fxlb
Copy link
Copy Markdown
Member

fxlb commented Dec 22, 2025

You could try:
$ git checkout -b save_geonet
$ git checkout -
$ git fetch upstream
$ git rebase upstream/master
$ git push -f

@infrastation
Copy link
Copy Markdown
Member

The most recent revision of print-geonet.c is recoverable from the commit you have pushed, but that commit is disjoint from the commit history. Let us know in case you need help fixing that.

@danieluliedguevara
Copy link
Copy Markdown
Author

Yes! I have a backup of everything. I've rebased my branch with upsteam/master.

But I think the PR should be reopened for the changes to take effect. Once that is done I'll make sure to push one clean commit with all the latest fixes. Thank you!

@fxlb
Copy link
Copy Markdown
Member

fxlb commented Dec 23, 2025

It seems this PR cannot be reopen.

@infrastation
Copy link
Copy Markdown
Member

Perhaps not whilst the proposed branch is out of shape.

@danieluliedguevara
Copy link
Copy Markdown
Author

I see what's happening... after the rebase and force push I changed the commit history and hence "current head isn't a descendant of the stored head sha" (Github Issue).

I think the cleanest way to procede would be to open a new PR. What do you think?

@infrastation
Copy link
Copy Markdown
Member

If that works for you, please do. Save your work somewhere first of all. Then you can make as many attempts to produce a clean commit in a fresh git clone as necessary.

@fxlb
Copy link
Copy Markdown
Member

fxlb commented Dec 25, 2025

Before opening a new PR, please [X] the "Allow edits by maintainers" button, to have the "Maintainers are allowed to edit this pull request." flag set. I want to test something.

@danieluliedguevara
Copy link
Copy Markdown
Author

Hello, last week I opened a new pull request. I added the "Allow edits by mantainers".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants